"use strict";
/*
 *  Copyright (C) 1998-2017 by Northwoods Software Corporation. All Rights Reserved.
 */
 
/**
  This class implements an inspector for GoJS model data objects.
  The constructor takes three arguments:
    {string} divid a string referencing the HTML ID of the to-be inspector's div.
    {Diagram} diagram a reference to a GoJS Diagram.
    {Object} options An optional JS Object describing options for the inspector.
 
  Options:
    inspectSelection {boolean} Default true, whether to automatically show and populate the Inspector
                               with the currently selected Diagram Part. If set to false, the inspector won't show anything
                               until you call Inspector.inspectObject(object) with a Part or JavaScript object as the argument.
    includesOwnProperties {boolean} Default true, whether to list all properties currently on the inspected data object.
    properties {Object} An object of string:Object pairs representing propertyName:propertyOptions.
                        Can be used to include or exclude additional properties.
    propertyModified function(propertyName, newValue) a callback
 
  Options for properties:
    show: {boolean|function} a boolean value to show or hide the property from the inspector, or a predicate function to show conditionally.
    readOnly: {boolean|function} whether or not the property is read-only
    type: {string} a string describing the data type. Supported values: "string|number|boolean|color|arrayofnumber|point|rect|size|spot|margin|select"
    defaultValue: {*} a default value for the property. Defaults to the empty string.
    choices: {Array|function} when type == "select", the Array of choices to use or a function that returns the Array of choices.
 
  Example usage of Inspector:
 
  var inspector = new Inspector("myInspector", myDiagram,
    {
      includesOwnProperties: false,
      properties: {
        "key": { show: Inspector.showIfPresent, readOnly: true },
        "comments": { show: Inspector.showIfNode  },
        "LinkComments": { show: Inspector.showIfLink },
        "chosen": { show: Inspector.showIfNode, type: "checkbox" },
        "state": { show: Inspector.showIfNode, type: "select", choices: ["Stopped", "Parked", "Moving"] }
      }
    });
 
  This is the basic HTML Structure that the Inspector creates within the given DIV element:
 
  <div id="divid" class="inspector">
    <tr>
      <td>propertyName</td>
      <td><input value=propertyValue /></td>
    </tr>
    ...
  </div>
 
*/
function Inspector(divid, diagram, options) {
    var mainDiv = document.getElementById(divid);
    mainDiv.className = "inspector";
    mainDiv.innerHTML = "";
    this._div = mainDiv;
    this._diagram = diagram;
    this._inspectedProperties = {};
 
    // Either a GoJS Part or a simple data object, such as Model.modelData
    this.inspectedObject = null;
 
    // Inspector options defaults:
    this.includesOwnProperties = true;
    this.declaredProperties = {};
    this.inspectsSelection = true;
    this.propertyModified = null;
 
    if (options !== undefined) {
        if (options["includesOwnProperties"] !== undefined) this.includesOwnProperties = options[
                "includesOwnProperties"];
        if (options["properties"] !== undefined) this.declaredProperties = options["properties"];
        if (options["inspectSelection"] !== undefined) this.inspectsSelection = options["inspectSelection"];
        if (options["propertyModified"] !== undefined) this.propertyModified = options["propertyModified"];
    }
 
    var self = this;
    diagram.addModelChangedListener(function (e) {
        if (e.isTransactionFinished) self.inspectObject();
    });
    if (this.inspectsSelection) {
        diagram.addDiagramListener("ChangedSelection", function (e) {
            self.inspectObject();
        });
    }
}
 
// Some static predicates to use with the "show" property.
Inspector.showIfNode = function (part) {
    return part instanceof go.Node
};
Inspector.showIfLink = function (part) {
    return part instanceof go.Link
};
Inspector.showIfGroup = function (part) {
    return part instanceof go.Group
};
 
// Only show the property if its present. Useful for "key" which will be shown on Nodes and Groups, but normally not on Links
Inspector.showIfPresent = function (data, propname) {
    if (data instanceof go.Part) data = data.data;
    return typeof data === "object" && data[propname] !== undefined;
};
 
/**
 * Update the HTML state of this Inspector given the properties of the {@link #inspectedObject}.
 * @param {Object} object is an optional argument, used when {@link #inspectSelection} is false to
 *                        set {@link #inspectedObject} and show and edit that object's properties.
 */
Inspector.prototype.inspectObject = function (object) {
    var inspectedObject = object;
    if (inspectedObject === undefined) {
        if (this.inspectsSelection)
            inspectedObject = this._diagram.selection.first();
        else
            inspectedObject = this.inspectedObject;
    }
 
    if (inspectedObject === null || this.inspectedObject === inspectedObject) {
        this.inspectedObject = inspectedObject;
        this.updateAllHTML();
        return;
    }
 
    this.inspectedObject = inspectedObject;
    if (inspectedObject === null) return;
    var mainDiv = this._div;
    mainDiv.innerHTML = "";
    // use either the Part.data or the object itself (for model.modelData)
    var data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
    if (!data) return;
    // Build table:
    var table = document.createElement("div");
    var tbody = document.createElement("div");
    this._inspectedProperties = {};
    this.tabIndex = 0;
    var declaredProperties = this.declaredProperties;
 
    // Go through all the properties passed in to the inspector and show them, if appropriate:
    for (var k in declaredProperties) {
        var val = declaredProperties[k];
        if (!this.canShowProperty(k, val, inspectedObject)) continue;
        var defaultValue = "";
        var title = val['title'] || k;
        if (val.defaultValue !== undefined) defaultValue = val.defaultValue;
        if (data[title] !== undefined) defaultValue = data[title];
        tbody.appendChild(this.buildPropertyRow(k, defaultValue || ""));
    }
    var Tarr={text:'节点名称',fzr:'负责人',fields:'字段权限'};
    var OwnProp = data;
    OwnProp['fzr'] = OwnProp['fzr'] ? OwnProp['fzr'] : '';
    OwnProp['fields'] = OwnProp['fields'] ? OwnProp['fields'] : {type:'number'};
    console.info(OwnProp);
    // Go through all the properties on the model data and show them, if appropriate:
    if (this.includesOwnProperties) {
        for (var k in data) {
            if (k === "__gohashid") continue; // skip internal GoJS hash property
            if (k === "loc") continue; // skip internal GoJS hash property
            if (k === "id") continue; // skip internal GoJS hash property
            if (k === "key") continue; // skip internal GoJS hash property
            if (this._inspectedProperties[k]) continue; // already exists
            if (declaredProperties[k] && !this.canShowProperty(k, declaredProperties[k], inspectedObject)) continue;
            tbody.appendChild(this.buildPropertyRow(Tarr[k] || '未定义属性', data[k]));
        }
    }
 
    table.appendChild(tbody);
    mainDiv.appendChild(table);
};
 
/**
 * @ignore
 * This predicate should be false if the given property should not be shown.
 * Normally it only checks the value of "show" on the property descriptor.
 * The default value is true.
 * @param {string} propertyName the property name
 * @param {Object} propertyDesc the property descriptor
 * @param {Object} inspectedObject the data object
 * @return {boolean} whether a particular property should be shown in this Inspector
 */
Inspector.prototype.canShowProperty = function (propertyName, propertyDesc, inspectedObject) {
    if (propertyDesc.show === false) return false;
    // if "show" is a predicate, make sure it passes or do not show this property
    if (typeof propertyDesc.show === "function") return propertyDesc.show(inspectedObject, propertyName);
    return true;
}
 
/**
 * @ignore
 * This predicate should be false if the given property should not be editable by the user.
 * Normally it only checks the value of "readOnly" on the property descriptor.
 * The default value is true.
 * @param {string} propertyName the property name
 * @param {Object} propertyDesc the property descriptor
 * @param {Object} inspectedObject the data object
 * @return {boolean} whether a particular property should be shown in this Inspector
 */
Inspector.prototype.canEditProperty = function (propertyName, propertyDesc, inspectedObject) {
    if (this._diagram.isReadOnly || this._diagram.isModelReadOnly) return false;
    // assume property values that are functions of Objects cannot be edited
    var data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
    var valtype = typeof data[propertyName];
    if (valtype === "function") return false;
    if (propertyDesc) {
        if (propertyDesc.readOnly === true) return false;
        // if "readOnly" is a predicate, make sure it passes or do not show this property
        if (typeof propertyDesc.readOnly === "function") return !propertyDesc.readOnly(inspectedObject, propertyName);
    }
    return true;
}
 
/**
 * @ignore
 * This sets this._inspectedProperties[propertyName] and creates the HTML table row:
 *    <tr>
 *      <td>propertyName</td>
 *      <td><input value=propertyValue /></td>
 *    </tr>
 * @param {string} propertyName the property name
 * @param {*} propertyValue the property value
 * @return the table row
 */
Inspector.prototype.buildPropertyRow = function (propertyName, propertyValue) {
    var mainDiv = this._div;
    //<input type="text" name="title" required  lay-verify="required" placeholder="请输入标题" autocomplete="off" class="layui-input">                 
    var tr = document.createElement("div");
    tr.className = "layui-form-item";
    var td1 = document.createElement("label");
    td1.className = "layui-form-label";
    td1.textContent = propertyName;
    tr.appendChild(td1);
 
    var td2 = document.createElement("div");
    td2.className = "layui-input-block";
    var decProp = this.declaredProperties[propertyName];
    var input = null;
    var self = this;
 
    function updateall() {
        self.updateAllProperties();
    }
 
    if (decProp && decProp.type === "select") {
        input = document.createElement("select");
        this.updateSelect(decProp, input, propertyName, propertyValue);
        input.addEventListener("change", updateall);
    } else {
        input = document.createElement("input");
        input.autocomplete ='off';
 
        input.value = this.convertToString(propertyValue);
        if (decProp) {
            var t = decProp.type;
            if (t !== 'string' && t !== 'number' && t !== 'boolean' &&
                t !== 'arrayofnumber' && t !== 'point' && t !== 'size' &&
                t !== 'rect' && t !== 'spot' && t !== 'margin') {
                input.setAttribute("type", decProp.type);
            }
            if (decProp.type === "color") {
                if (input.type === "color") {
                    input.value = this.convertToColor(propertyValue);
                    input.addEventListener("input", updateall);
                    input.addEventListener("change", updateall);
                }
            }
            if (decProp.type === "checkbox") {
                input.checked = !! propertyValue;
                input.addEventListener("change", updateall);
            }
        }
        if (input.type !== "color") input.addEventListener("blur", updateall);
    }
 
    if (input) {
        input.className = 'layui-input';
        input.tabIndex = this.tabIndex++;
        input.disabled = !this.canEditProperty(propertyName, decProp, this.inspectedObject);
        td2.appendChild(input);
    }
    tr.appendChild(td2);
 
    this._inspectedProperties[propertyName] = input;
    return tr;
};
 
/**
 * @ignore
 * HTML5 color input will only take hex,
 * so let HTML5 canvas convert the color into hex format.
 * This converts "rgb(255, 0, 0)" into "#FF0000", etc.
 * @param {string} propertyValue
 * @return {string}
 */
Inspector.prototype.convertToColor = function (propertyValue) {
    var ctx = document.createElement("canvas").getContext("2d");
    ctx.fillStyle = propertyValue;
    return ctx.fillStyle;
};
 
/**
 * @ignore
 * @param {string}
 * @return {Array.<number>}
 */
Inspector.prototype.convertToArrayOfNumber = function (propertyValue) {
    if (propertyValue === "null") return null;
    var split = propertyValue.split(' ');
    var arr = [];
    for (var i = 0; i < split.length; i++) {
        var str = split[i];
        if (!str) continue;
        arr.push(parseFloat(str));
    }
    return arr;
};
 
/**
 * @ignore
 * @param {*}
 * @return {string}
 */
Inspector.prototype.convertToString = function (x) {
    if (x === undefined) return "undefined";
    if (x === null) return "null";
    if (x instanceof go.Point) return go.Point.stringify(x);
    if (x instanceof go.Size) return go.Size.stringify(x);
    if (x instanceof go.Rect) return go.Rect.stringify(x);
    if (x instanceof go.Spot) return go.Spot.stringify(x);
    if (x instanceof go.Margin) return go.Margin.stringify(x);
    if (x instanceof go.List) return this.convertToString(x.toArray());
    if (Array.isArray(x)) {
        var str = "";
        for (var i = 0; i < x.length; i++) {
            if (i > 0) str += " ";
            var v = x[i];
            str += this.convertToString(v);
        }
        return str;
    }
    return x.toString();
};
 
/**
 * @ignore
 * Update all of the HTML in this Inspector.
 */
Inspector.prototype.updateAllHTML = function () {
    var inspectedProps = this._inspectedProperties;
    var diagram = this._diagram;
    var isPart = this.inspectedObject instanceof go.Part;
    var data = isPart ? this.inspectedObject.data : this.inspectedObject;
    if (!data) { // clear out all of the fields
        for (var name in inspectedProps) {
            var input = inspectedProps[name];
            if (input instanceof HTMLSelectElement) {
                input.innerHTML = "";
            } else if (input.type === "color") {
                input.value = "#000000";
            } else if (input.type === "checkbox") {
                input.checked = false;
            } else {
                input.value = "";
            }
 
        }
    } else {
        for (var name in inspectedProps) {
            var input = inspectedProps[name];
            var propertyValue = data[name];
            if (input instanceof HTMLSelectElement) {
                var decProp = this.declaredProperties[name];
                this.updateSelect(decProp, input, name, propertyValue);
            } else if (input.type === "color") {
                input.value = this.convertToColor(propertyValue);
            } else if (input.type === "checkbox") {
                input.checked = !! propertyValue;
            } else {
                input.value = this.convertToString(propertyValue);
            }
        }
    }
}
 
/**
 * @ignore
 * Update an HTMLSelectElement with an appropriate list of choices, given the propertyName
 */
Inspector.prototype.updateSelect = function (decProp, select, propertyName, propertyValue) {
    select.innerHTML = ""; // clear out anything that was there
    var choices = decProp.choices;
    if (typeof choices === "function") choices = choices(this.inspectedObject, propertyName);
    if (!Array.isArray(choices)) choices = [];
    decProp.choicesArray = choices; // remember list of actual choice values (not strings)
    for (var i = 0; i < choices.length; i++) {
        var choice = choices[i];
        var opt = document.createElement("option");
        opt.text = this.convertToString(choice);
        select.add(opt, null);
    }
    select.value = this.convertToString(propertyValue);
}
 
/**
 * @ignore
 * Update all of the data properties of {@link #inspectedObject} according to the
 * current values held in the HTML input elements.
 */
Inspector.prototype.updateAllProperties = function () {
    var inspectedProps = this._inspectedProperties;
    var diagram = this._diagram;
    var isPart = this.inspectedObject instanceof go.Part;
    var data = isPart ? this.inspectedObject.data : this.inspectedObject;
    if (!data) return; // must not try to update data when there's no data!
 
    diagram.startTransaction("set all properties");
    for (var name in inspectedProps) {
        var input = inspectedProps[name];
        var value = input.value;
 
        // don't update "readOnly" data properties
        var decProp = this.declaredProperties[name];
        if (!this.canEditProperty(name, decProp, this.inspectedObject)) continue;
 
        // If it's a boolean, or if its previous value was boolean,
        // parse the value to be a boolean and then update the input.value to match
        var type = "";
        if (decProp !== undefined && decProp.type !== undefined) {
            type = decProp.type;
        }
        if (type === "") {
            var oldval = data[name];
            if (typeof oldval === "boolean") type = "boolean"; // infer boolean
            else if (typeof oldval === "number") type = "number";
            else if (oldval instanceof go.Point) type = "point";
            else if (oldval instanceof go.Size) type = "size";
            else if (oldval instanceof go.Rect) type = "rect";
            else if (oldval instanceof go.Spot) type = "spot";
            else if (oldval instanceof go.Margin) type = "margin";
        }
 
        // convert to specific type, if needed
        switch (type) {
        case "boolean":
            value = !(value == false || value === "false" || value === "0");
            break;
        case "number":
            value = parseFloat(value);
            break;
        case "arrayofnumber":
            value = this.convertToArrayOfNumber(value);
            break;
        case "point":
            value = go.Point.parse(value);
            break;
        case "size":
            value = go.Size.parse(value);
            break;
        case "rect":
            value = go.Rect.parse(value);
            break;
        case "spot":
            value = go.Spot.parse(value);
            break;
        case "margin":
            value = go.Margin.parse(value);
            break;
        case "checkbox":
            value = input.checked;
            break;
        case "select":
            value = decProp.choicesArray[input.selectedIndex];
            break;
        }
 
        // in case parsed to be different, such as in the case of boolean values,
        // the value shown should match the actual value
        input.value = value;
 
        // modify the data object in an undo-able fashion
        diagram.model.setDataProperty(data, name, value);
 
        // notify any listener
        if (this.propertyModified !== null) this.propertyModified(name, value, this);
    }
    diagram.commitTransaction("set all properties");
};